﻿using Apewer.Network;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;

namespace Apewer.AspNetBridge
{

    [Serializable]
    internal class RouteItem : IToJson
    {

        public string Path;

        public string Lower;

        public Type Controller;

        public MethodInfo Method;

        public string MethodName;

        public bool Connect;
        public bool Delete;
        public bool Get;
        public bool Head;
        public bool Options;
        public bool Patch;
        public bool Post;
        public bool Put;
        public bool Trace;

        public ParameterInfo[] Parameters;

        public Type Return;

        public Json ToJson()
        {
            var ps = Json.NewObject();
            foreach (var parameter in Parameters) ps.SetProperty(parameter.Name, parameter.ParameterType.FullName);

            var methods = Json.NewArray();
            if (Connect) methods.AddItem("connect");
            if (Delete) methods.AddItem("delete");
            if (Get) methods.AddItem("get");
            if (Head) methods.AddItem("head");
            if (Options) methods.AddItem("options");
            if (Patch) methods.AddItem("patch");
            if (Post) methods.AddItem("post");
            if (Put) methods.AddItem("put");
            if (Trace) methods.AddItem("trace");

            var json = Json.NewObject();
            json.SetProperty("path", Path);
            json.SetProperty("controller", Controller.FullName);
            json.SetProperty("function", Method.Name);
            json.SetProperty("return", Return.FullName);
            json.SetProperty("parameters", ps);
            json.SetProperty("methods", methods);

            return json;
        }

        #region Enumerate

        internal static RouteItem[] Parse(IEnumerable<Assembly> assemblies, bool withVoid)
        {
            if (assemblies.IsEmpty()) assemblies = AppDomain.CurrentDomain.GetAssemblies();
            var baseType = typeof(ApiController);
            var items = new ArrayBuilder<RouteItem>();
            foreach (var assembly in assemblies)
            {
                var types = assembly.GetExportedTypes();
                foreach (var type in types)
                {
                    if (type.IsNotPublic) continue;
                    if (!type.IsClass) continue;
                    if (!RuntimeUtility.IsInherits(type, baseType)) continue;

                    if (type.Name == "AccountController")
                    {
                    }

                    var pa = RuntimeUtility.GetAttribute<RoutePrefixAttribute>(type, false);
                    var prefix = (pa == null || pa.Path.IsEmpty()) ? null : pa.Path.Split('/');

                    var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance);
                    foreach (var method in methods)
                    {
                        if (method.IsConstructor) continue;
                        if (method.IsGenericMethod) continue;
                        if (!withVoid)
                        {
                            var returnType = method.ReturnType;
                            if (returnType == null || returnType.Equals(typeof(void))) continue;
                        }

                        var route = RuntimeUtility.GetAttribute<RouteAttribute>(method);
                        if (route == null) continue;
                        
                        var path = route.Path;
                        if (path.IsEmpty()) path = method.Name;
                        if (path.IsEmpty()) continue;
                        path = Concat(prefix, path.Split('/'));

                        var item = new RouteItem();
                        item.Controller = type;
                        item.Method = method;
                        item.MethodName = method.Name;
                        item.Parameters = method.GetParameters();
                        item.Return = method.ReturnType;
                        item.Path = path;
                        item.Lower = path.Lower();

                        item.Get = RuntimeUtility.Contains<HttpGetAttribute>(method);
                        item.Post = RuntimeUtility.Contains<HttpPostAttribute>(method);

                        item.Connect = RuntimeUtility.Contains<HttpConnectAttribute>(method);
                        item.Delete = RuntimeUtility.Contains<HttpDeleteAttribute>(method);
                        item.Head = RuntimeUtility.Contains<HttpHeadAttribute>(method);
                        item.Options = RuntimeUtility.Contains<HttpOptionsAttribute>(method);
                        item.Patch = RuntimeUtility.Contains<HttpPatchAttribute>(method);
                        item.Put = RuntimeUtility.Contains<HttpPutAttribute>(method);
                        item.Trace = RuntimeUtility.Contains<HttpTraceAttribute>(method);

                        items.Add(item);
                    }
                }
            }
            return items.Export();
        }

        internal static RouteItem Match(RouteItem[] routes, string path, HttpMethod method)
        {
            if (routes == null) return null;
            if (path.IsEmpty()) path = "/";

            var length = routes.Length;
            for (var i = 0; i < length; i++)
            {
                var route = routes[i];
                if (route == null) continue;
                if (route.Path != path) continue;

                if (method == HttpMethod.GET && route.Get) return route;
                if (method == HttpMethod.POST && route.Post) return route;
                if (method == HttpMethod.CONNECT && route.Connect) return route;
                if (method == HttpMethod.DELETE && route.Delete) return route;
                if (method == HttpMethod.HEAD && route.Head) return route;
                if (method == HttpMethod.OPTIONS && route.Options) return route;
                if (method == HttpMethod.PATCH && route.Patch) return route;
                if (method == HttpMethod.PUT && route.Put) return route;
                if (method == HttpMethod.TRACE && route.Trace) return route;
            }

            var lower = path.Lower();
            for (var i = 0; i < length; i++)
            {
                var route = routes[i];
                if (route == null) continue;
                if (route.Lower != lower) continue;

                if (method == HttpMethod.GET && route.Get) return route;
                if (method == HttpMethod.POST && route.Post) return route;
                if (method == HttpMethod.CONNECT && route.Connect) return route;
                if (method == HttpMethod.DELETE && route.Delete) return route;
                if (method == HttpMethod.HEAD && route.Head) return route;
                if (method == HttpMethod.OPTIONS && route.Options) return route;
                if (method == HttpMethod.PATCH && route.Patch) return route;
                if (method == HttpMethod.PUT && route.Put) return route;
                if (method == HttpMethod.TRACE && route.Trace) return route;
            }

            return null;
        }

        static string Concat(string[] prefix, string[] path)
        {
            var all = new List<string>(16);

            if (prefix != null) all.AddRange(prefix);
            if (path != null) all.AddRange(path);

            var segs = new List<string>(all.Count);
            foreach (var seg in all)
            {
                if (seg.IsEmpty()) continue;
                segs.Add(seg);
            }
            if (segs.Count < 1) return "/";
            return "/" + TextUtility.Join("/", segs.ToArray());
        }

        #endregion

    }

}
